标签: API 设计
公共接口与已发布接口
许多现代语言都区分模块中的公共特性和私有特性。但公共特性和已发布特性之间的区别却没有那么明显:这可能是一个更重要的区别。
重构模块依赖关系
随着程序规模的增长,将其拆分为模块非常重要,这样您就不需要了解所有内容就可以进行小的修改。这些模块通常可以由不同的团队提供并动态组合。在这篇重构文章中,我使用表示层-领域层-数据层对一个小程序进行了拆分。然后,我重构了这些模块之间的依赖关系,以引入服务定位器和依赖注入模式。这些模式适用于不同的语言,但看起来有所不同,因此我将分别使用 Java 和无类 JavaScript 风格展示这些重构。
集合管道
集合管道是一种编程模式,您可以在其中将某些计算组织为一系列操作,这些操作通过将一个操作的输出作为集合输入到下一个操作来组合。(常见操作有 filter、map 和 reduce。)这种模式在函数式编程中很常见,在具有 lambda 表达式的面向对象语言中也很常见。本文通过几个如何形成管道的示例来描述该模式,既向不熟悉该模式的人介绍该模式,也帮助人们理解核心概念,以便他们可以更轻松地将一种语言中的想法应用到另一种语言中。
微服务与分布式对象第一定律
在 EAA 的 P 中,我说过“不要分发你的对象”。这个建议是否与我对微服务的兴趣相矛盾?
API 不应受版权保护
API 不应受版权保护,以便程序员可以重新实现接口以支持测试、互操作性,并鼓励竞争。
双时态历史记录
访问某些属性的历史值通常是必要的。但有时,为了响应追溯更新,需要修改历史记录本身。双时态历史记录将时间视为两个维度:实际历史记录记录了在完美传输信息的情况下应该是什么样的历史记录,而记录历史记录则记录了我们对历史记录的了解是如何变化的。
CQRS
CQRS 代表命令查询职责分离。这是我第一次听到 Greg Young 描述的一种模式。其核心是,您可以使用与读取信息所用模型不同的模型来更新信息。对于某些情况,这种分离可能很有价值,但请注意,对于大多数系统来说,CQRS 会增加风险复杂性。
面向命令的接口
模块最常见的接口风格是使用过程或对象方法。因此,如果您希望模块计算合同的一系列费用,则可以使用 BillingService 类及其用于执行计算的方法,如下所示调用它
aBillingService.calculateCharges(aContract)
面向命令的接口将为每个操作提供一个命令类,并使用如下所示的内容调用
CalculateChargeCommand.new(aContract).run()
命令查询分离
“命令查询分离”一词是由 Bertrand Meyer 在他的著作《面向对象软件构造》中提出的,这本书是面向对象早期最具影响力的面向对象书籍之一。(第一版是具有影响力的一版,第二版也不错,但在你能举起它之前,你需要在健身房里锻炼几个月。)
构造函数初始化
构造函数初始化是一种方法,您可以在对象的创建方法中传入对象所需的所有协作者。它是SetterInitialization 的替代方法。
礼貌实现
当你编写一个类时,你主要努力确保该类的特性对该类有意义。但在某些情况下,添加一个特性以允许类符合它自然应该符合的更丰富的接口是有意义的。
设计继承
面向对象领域中持续时间最长的争论之一是开放继承和设计继承之间的争论。设计继承的原则可能最好地概括为Josh Bloch 的话:“为继承而设计和记录,否则就禁止它”。使用这种方法,您需要注意决定哪些方法可以继承,并密封其他方法以防止它们被覆盖。
鸭子接口
也许我太天真了,但我从未想过我在人性化接口上的帖子会引起如此多的讨论。可悲的是,大多数讨论最终都变成了关于 Ruby 的 Array 和 Java 的 List 的相对优点的争论,而不是我试图提出的基本观点,但尽管如此,我认为出现了一些很好的对话支流。
其中一个对话主题指出,除了人性化/最小化理念之外,Array 和 List 之间存在差异还有其他原因。其中一个原因与类似功能在两种语言中扮演不同角色的方式有关。
标志参数
标志参数是一种函数参数,它告诉函数根据其值执行不同的操作。假设我们要预订一场音乐会。有两种方式可以做到这一点:普通和高级。要在这里使用标志参数,我们最终会得到一个方法声明,如下所示
流畅接口
几个月前,我参加了Eric Evans 的一个研讨会,他谈到了一种我们决定将其命名为流畅接口的特定接口风格。这不是一种常见的风格,但我们认为应该让更多人知道。也许最好的描述方式是举例说明。
基础平台
基础平台是在构建在其之上的任何应用程序之前构建的。其理念是,您分析需要平台的各种应用程序的需求,然后构建平台。平台完成后,您就可以在其之上构建应用程序。关键是在您开始应用程序的工作之前,平台确实需要有一个稳定的 API,否则对平台的更改将难以管理,因为它们会对应用程序产生连锁反应。
Getter 终结者
当他们看到 getter 方法时,你可以通过他们嘴角左侧的抽搐认出他们,他们会迅速拔出战斧,发出满意的叫喊声,因为另一个 getter 被无情地从一个类中砍了下来,而这个类立即在男子气概的 Getter 终结者的脚下欣喜若狂地感激涕零。
收获平台
要通过收获来构建平台,首先不要试图构建平台,而是构建一个应用程序。在构建应用程序时,您不要试图开发通用代码,而是努力构建一个结构良好、设计良好的应用程序。
头文件接口
头文件接口是一种显式接口,它模仿类的隐式公共接口。本质上,您需要获取类的所有公共方法,并在接口中声明它们。然后,您可以为该类提供另一种实现。这与角色接口相反——我在那里讨论了更多细节以及优缺点。
人性化接口
在 Ruby 圈子里待了一段时间后,我经常听到“人性化接口”这个词。它描述了 Rubyist 编写类接口的一部分态度,我认为它也建立了两种 API 设计思想流派之间有趣的对比(另一种是最小化接口)。
隐式接口实现
Java 和 C# 都共享相同的纯接口类型模型。您可以通过使用 interface Mailable
声明一个纯接口,然后您可以使用 class Customer implements Mailable
(在 Java 中)声明您实现了它。一个类可以实现任意数量的纯接口。这种模型忽略的一件事是,只要您有一个类,就会有隐式接口。
接口实现对
采用每个类并将其与接口配对的做法。因此,您会看到成对出现的事物——可能是 ICustomer 和 Customer,或者 Customer 和 CustomerImpl。在许多方面,它都反映了 C/C++ 为每个类使用头文件的习惯,尽管在这种情况下,接口和实现实际上是独立的类型。
控制反转
控制反转是您在扩展框架时经常遇到的一种常见现象。事实上,它通常被视为框架的一个定义特征。
重载 Getter Setter
我最近一直在研究 JavaScript,令我震惊的一件事是使用相同的函数名作为 getter 和 setter 的习惯。因此,如果你想在 jQuery 中找出横幅的高度,你可以使用 $("#banner").height()
,如果你想更改高度,你可以使用 $("#banner").height(100)
。
我对这种约定很熟悉,因为它被 Smalltalk 使用过。你可能会使用 banner height
获取一个值,并使用 banner height: 100
更改它。知道这是一个 Smalltalk 约定就足以让我喜欢它,因为我对这种语言有着遥远但持久的热爱。但即使是最好的东西也有缺陷,我无法掩饰我对这种编码风格的厌恶。
并行变更
对影响所有使用者的接口进行更改需要两种思维模式:实现更改本身,然后更新所有使用位置。当你尝试同时完成这两件事时,这可能会很困难,特别是当更改是在具有多个或外部客户端的已发布接口上进行时。
**并行变更**,也称为**扩展和收缩**,是一种以安全的方式实现接口向后不兼容更改的模式,它将更改分为三个不同的阶段:扩展、迁移和收缩。
已发布接口
*已发布接口*是我使用的一个术语(首先是在重构中),指的是在定义它的代码库之外使用的类接口。因此,它在 Java 中的含义比 public 更广泛,实际上甚至比 C# 中的非内部 public 更广泛。我在 IEEE 软件专栏中指出,已发布和公共之间的区别实际上比公共和私有之间的区别更重要。
必需接口
必需接口是由交互的客户端定义的接口,它指定了供应商组件需要做什么才能在该交互中使用。
规则引擎
我应该使用规则引擎吗?
密封
密封方法或类会阻止子类覆盖它。
Setter 初始化
使用 setter 初始化,你可以构造一个空对象,然后使用 setter 方法设置各种属性。(构造函数初始化的替代方法。)
告诉,不要问
告诉,不要问是一个原则,可以帮助人们记住面向对象是关于将数据与其操作函数捆绑在一起。它提醒我们,不要向对象询问数据并根据该数据采取行动,而是应该告诉对象该做什么。这鼓励将行为移动到对象中以与数据一起使用。
两件难事
计算机科学中只有两件难事:缓存失效和命名。
-- 菲尔·卡尔顿
类型化集合
当人们开始使用对象时,特别是在强类型语言中,一个常见的问题是他们是否应该为不同的域类型使用特定的集合类。因此,如果你有一个公司类存储了一个员工集合,你应该使用库中的常规集合类,还是应该创建一个特定的 EmployeeList
类 - 一个类型化集合。
统一访问原则
模块提供的所有服务都应该可以通过统一的符号使用,而不会泄露它们是通过存储还是计算实现的。
-- 贝特朗·迈耶
贝特朗·迈耶在他极具影响力的著作面向对象软件构造中提出了这一原则。
该原则的要点是,如果你有一个 person 对象,并且你询问它的年龄,你应该使用相同的符号,无论年龄是对象的存储字段还是计算值。这实际上意味着 person 的客户端不应该知道也不应该关心年龄是存储的还是计算的。
用户定义字段
软件系统中的一个常见功能是允许用户在数据结构中定义自己的字段。考虑一个地址簿 - 你可能想添加很多东西。随着每天都有新的社交网络出现,用户可能希望为他们的联系人添加一个 Bunglr ID 的新字段。
值对象
在编程时,我经常发现将事物表示为复合体很有用。二维坐标由 x 值和 y 值组成。金额由数字和货币组成。日期范围由开始日期和结束日期组成,它们本身可以是年、月和日的复合体。
在这样做的过程中,我遇到了两个复合对象是否相同的问题。如果我有两个点对象都表示笛卡尔坐标 (2,3),那么将它们视为相等是有意义的。由于其属性值(在本例中为其 x 和 y 坐标)而相等的对象称为值对象。